package com.flow.framework.config.config;

import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.flow.framework.common.error.SystemErrorCode;
import com.flow.framework.common.exception.CheckedException;
import com.flow.framework.common.stream.SafeBufferedReader;
import com.flow.framework.common.util.io.IoUtil;
import com.flow.framework.common.util.verify.VerifyUtil;
import com.flow.framework.config.service.system.health.impl.NacosConfigHealthCheckServiceImpl;
import com.flow.framework.config.system.listener.SystemRefreshListener;
import com.flow.framework.core.constant.FrameworkCoreConstant;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.bootstrap.BootstrapConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * 框架配置中心配置
 *
 * @author luoguopiao
 * @version 0.0.1
 * @date 2022/3/5
 */
@Slf4j
@BootstrapConfiguration
@RequiredArgsConstructor
public class FrameworkConfigConfig {

    private static final String CLOUD_PROPERTIES_LINUX = "/opt/flow/config/cloud.properties";

    private static final String CLOUD_PROPERTIES_WINDOWS = "C:/opt/flow/config/cloud.properties";

    private final Environment environment;

    /**
     * 为了兼容自定义配置文件cloud.properties中的配置
     * <p>
     * 假如spring.profiles.active为test，则cloud.properties中的配置如下：
     * <p>
     * test.config.server=127.0.0.1:8848
     * test.config.namespace=customization
     * test.config.group=test-group
     * test.config.username=username
     * test.config.password=password
     *
     * @return
     */
    @Bean
    @ConditionalOnMissingBean
    NacosConfigProperties nacosConfigProperties() {
        String springActiveProfile = environment.resolveRequiredPlaceholders("${" + FrameworkCoreConstant.SERVICE_ACTIVE_PROFILES_KEY + ":}");
        String applicationName = environment.resolveRequiredPlaceholders("${" + FrameworkCoreConstant.SERVICE_NAME_KEY + ":}");

        NacosConfigProperties nacosConfigProperties = new NacosConfigProperties();
        nacosConfigProperties.setFileExtension("properties");
        nacosConfigProperties.setEncode(StandardCharsets.UTF_8.name());

        String serverAddr = environment.resolveRequiredPlaceholders("${spring.cloud.nacos.config.server-addr:}");
        if (VerifyUtil.isEmpty(serverAddr)) {
            serverAddr = environment.resolveRequiredPlaceholders("${spring.cloud.nacos.server-addr:}");
        }
        if (!VerifyUtil.isEmpty(serverAddr)) {
            nacosConfigProperties.setServerAddr(serverAddr);

            String namespace = environment.resolveRequiredPlaceholders("${spring.cloud.nacos.config.namespace:}");
            if (!VerifyUtil.isEmpty(namespace)) {
                nacosConfigProperties.setNamespace(namespace);
            }

            String group = environment.resolveRequiredPlaceholders("${spring.cloud.nacos.config.group:}");
            if (!VerifyUtil.isEmpty(group)) {
                nacosConfigProperties.setGroup(group);
            }

            String userName = environment.resolveRequiredPlaceholders("${spring.cloud.nacos.config.username:}");
            if (!VerifyUtil.isEmpty(userName)) {
                nacosConfigProperties.setUsername(userName);
            }

            String password = environment.resolveRequiredPlaceholders("${spring.cloud.nacos.config.password:}");
            if (!VerifyUtil.isEmpty(password)) {
                nacosConfigProperties.setPassword(password);
            }
            adaptNacosConfigProperties(applicationName, springActiveProfile, nacosConfigProperties);
            return nacosConfigProperties;
        }
        loadFromCloudConfig(applicationName, springActiveProfile, nacosConfigProperties);
        return nacosConfigProperties;
    }

    private void adaptNacosConfigProperties(String applicationName, String springActiveProfile,
                                            NacosConfigProperties nacosConfigProperties) {
        String namespace = nacosConfigProperties.getNamespace();
        String group = nacosConfigProperties.getGroup();
        String username = nacosConfigProperties.getUsername();
        String password = nacosConfigProperties.getPassword();
        String dataId = applicationName + ".properties";
        Properties configServerProperties = new Properties();

        configServerProperties.put("serverAddr", nacosConfigProperties.getServerAddr());
        if (!VerifyUtil.isEmpty(namespace)) {
            configServerProperties.put("namespace", namespace);
        }
        if (!VerifyUtil.isEmpty(username)) {
            configServerProperties.put("username", username);
        }
        if (!VerifyUtil.isEmpty(password)) {
            configServerProperties.put("password", password);
        }
        Properties tempProp;
        if (VerifyUtil.isEmpty(group)) {
            nacosConfigProperties.setGroup(springActiveProfile);
            tempProp = getRemoteProperties(dataId, springActiveProfile, configServerProperties);
        } else {
            nacosConfigProperties.setGroup(group);
            tempProp = getRemoteProperties(dataId, group, configServerProperties);
        }
        List<NacosConfigProperties.Config> extensionConfigs = new ArrayList<>();
        List<NacosConfigProperties.Config> sharedConfigs = new ArrayList<>();
        int loopIndex = 0;
        while (true) {
            String remoteDataId = tempProp.getProperty("spring.cloud.nacos.config.extension-configs[" + loopIndex + "].data-id");
            if (VerifyUtil.isEmpty(remoteDataId)) {
                remoteDataId = tempProp.getProperty("spring.cloud.nacos.config.extensionConfigs[" + loopIndex + "].dataId");
            }
            if (VerifyUtil.isEmpty(remoteDataId)) {
                break;
            }

            String remoteGroup = tempProp.getProperty("spring.cloud.nacos.config.extension-configs[" + loopIndex + "].group");
            if (VerifyUtil.isEmpty(remoteGroup)) {
                remoteGroup = tempProp.getProperty("spring.cloud.nacos.config.extensionConfigs[" + loopIndex + "].group", springActiveProfile);
            }
            NacosConfigProperties.Config extensionConfig = new NacosConfigProperties.Config(remoteDataId, remoteGroup, true);
            extensionConfigs.add(extensionConfig);
            loopIndex++;
        }

        loopIndex = 0;
        while (true) {
            String remoteDataId = tempProp.getProperty("spring.cloud.nacos.config.shared-configs[" + loopIndex + "].data-id");
            if (VerifyUtil.isEmpty(remoteDataId)) {
                remoteDataId = tempProp.getProperty("spring.cloud.nacos.config.sharedConfigs[" + loopIndex + "].dataId");
            }
            if (VerifyUtil.isEmpty(remoteDataId)) {
                break;
            }

            String remoteGroup = tempProp.getProperty("spring.cloud.nacos.config.shared-configs[" + loopIndex + "].group");
            if (VerifyUtil.isEmpty(remoteGroup)) {
                remoteGroup = tempProp.getProperty("spring.cloud.nacos.config.sharedConfigs[" + loopIndex + "].group", springActiveProfile);
            }
            NacosConfigProperties.Config sharedConfig = new NacosConfigProperties.Config(remoteDataId, remoteGroup, true);
            sharedConfigs.add(sharedConfig);
            loopIndex++;
        }
        nacosConfigProperties.setExtensionConfigs(extensionConfigs);
        nacosConfigProperties.setSharedConfigs(sharedConfigs);
    }

    private void loadFromCloudConfig(String applicationName, String springActiveProfile,
                                     NacosConfigProperties nacosConfigProperties) {
        File cloudConfig;
        String osName = System.getProperty("os.name");
        if (VerifyUtil.isEmpty(osName)) {
            throw new CheckedException(SystemErrorCode.PARAMS_ERROR, "can't find operator system name.");
        }
        if (osName.toUpperCase().contains(FrameworkCoreConstant.WINDOWS_OS)) {
            cloudConfig = new File(CLOUD_PROPERTIES_WINDOWS);
        } else {
            cloudConfig = new File(CLOUD_PROPERTIES_LINUX);
        }
        if (!cloudConfig.exists() || !cloudConfig.canRead()) {
            throw new CheckedException(SystemErrorCode.SYSTEM_START_ERROR, "can't find cloud properties file.");
        }

        if (VerifyUtil.isEmpty(springActiveProfile)) {
            throw new CheckedException(SystemErrorCode.SYSTEM_START_ERROR, "spring.profiles.active is empty.");
        }

        Properties properties = new Properties();
        BufferedReader bufferedReader = null;
        try {
            bufferedReader = new SafeBufferedReader(new FileReader(cloudConfig));
            properties.load(bufferedReader);
        } catch (IOException e) {
            log.error("read cloud config file error. error: ", e);
            throw new CheckedException(SystemErrorCode.SYSTEM_START_ERROR, "read cloud properties file.", e);
        } finally {
            IoUtil.close(bufferedReader);
        }

        String serverAddr = properties.getProperty(springActiveProfile + ".config.server");
        if (VerifyUtil.isEmpty(serverAddr)) {
            throw new CheckedException(SystemErrorCode.PARAMS_ERROR, "can't find config server address.");
        }
        nacosConfigProperties.setServerAddr(serverAddr);

        String namespace = properties.getProperty(springActiveProfile + ".config.namespace");
        if (!VerifyUtil.isEmpty(namespace)) {
            nacosConfigProperties.setNamespace(namespace);
        }

        String group = properties.getProperty(springActiveProfile + ".config.group");
        if (!VerifyUtil.isEmpty(group)) {
            nacosConfigProperties.setGroup(group);
        }

        String userName = properties.getProperty(springActiveProfile + ".config.username");
        if (!VerifyUtil.isEmpty(userName)) {
            nacosConfigProperties.setUsername(userName);
        }

        String password = properties.getProperty(springActiveProfile + ".config.password");
        if (!VerifyUtil.isEmpty(password)) {
            nacosConfigProperties.setPassword(password);
        }
        adaptNacosConfigProperties(applicationName, springActiveProfile, nacosConfigProperties);
    }

    private Properties getRemoteProperties(String dataId, String springActiveProfile, Properties configServerProperties) {
        Properties tempProp = new Properties();
        ByteArrayInputStream inputStream = null;
        try {
            ConfigService configService = NacosFactory.createConfigService(configServerProperties);
            String content = configService.getConfig(dataId, springActiveProfile, 5000);
            if (VerifyUtil.isEmpty(content)) {
                throw new CheckedException(SystemErrorCode.SYSTEM_START_ERROR);
            }
            inputStream = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
            tempProp.load(inputStream);
        } catch (Exception e) {
            log.error("get nacos config file error. error: ", e);
            throw new CheckedException(SystemErrorCode.SYSTEM_START_ERROR, "get config file error.", e);
        } finally {
            IoUtil.close(inputStream);
        }
        return tempProp;
    }

    @Bean
    @ConditionalOnMissingBean
    NacosConfigHealthCheckServiceImpl nacosConfigHealthCheckService(NacosConfigManager nacosConfigManager) {
        return new NacosConfigHealthCheckServiceImpl(nacosConfigManager);
    }

    @Bean
    @ConditionalOnMissingBean
    SystemRefreshListener systemRefreshListener() {
        return new SystemRefreshListener();
    }
}
